/**
 * ImageApplication.java
 * Author: 
 * Description:
 * 	Main application class.  This application
 * 	implements 3 basic image operations:
 * 	- threshold
 * 	- image negative
 * 	- contrast stretching
 *
 * 	Code based of same code provided by Prof. Grewe.
 */

import java.util.*;

public class ImageApplication
{
	// This uses generics as the old vector use (non-generics)
	// is deprecated.  This is similar to templates in C++.
	private Vector<IPWindow> windows;

	public ImageApplication()
	{
		windows = new Vector<IPWindow>();
		createWindow();
	}

	////
	// Create window without any image loaded.
	public void createWindow()
	{
		// Create window and add to our list
		IPWindow ipwin = new IPWindow(this);
		windows.add(ipwin);

		// Extract index of this window and set it
		int winid = windows.indexOf(ipwin); 
		ipwin.setWindowId(winid);

		p.print("Created window: " + winid);
	}

	////
	// Create window using specified image and ROI
	public void createWindow(ImageData img, int x, int y, int w, int h)
	{
		// Create window and add to our list
		IPWindow ipwin = new IPWindow(this, img, x, y, w, h);
		windows.add(ipwin);

		// Extract index of this window and set it
		int winid = windows.indexOf(ipwin); 
		ipwin.setWindowId(winid);

		p.print("created window: " + winid);
	}

	////
	// Call to close a specified window.
	public void closeWindow(int windowID)
	{
		p.print("Closing window: " + windowID);

		// Notify window it is closing
		IPWindow win = windows.elementAt(windowID);
		win.notifyWindowIsClosing();

		// Remove window from this list.
		windows.remove(windowID);

		// Reassign window ids
		reassignWindowIDs();

		// Exit if no windows left.
		if (windows.size() < 1)
		{
			System.exit(0);
		}
	}

	////
	// Reassigns window ids to the windows.
	// This is necessary when removing an element in the middle
	// of the list, as indicies will change due to elements
	// being shifted.
	public void reassignWindowIDs()
	{
		for (int i = 0; i < windows.size(); i++)
		{
			IPWindow win = windows.elementAt(i);
			win.setWindowId(i);
		}
	}

	public void threshold(int tvalue, IPWindow win)
	{
		p.print("Applying threshold value: " + tvalue);

		ImageData new_image = win.getActiveImage().threshold(tvalue);
		win.setActiveImage(new_image);
	}

	public void imageNegative(IPWindow win)
	{
		p.print("Applying image negative");

		// Get pixels
		int width = win.getActiveImage().getWidth();
		int height = win.getActiveImage().getHeight();
		int [] pixels = new int[width * height];
		win.getActiveImage().getPixels(pixels, 0, 0,
				width, height);

		// Modify pixels.
		for (int y=0; y < height; y++)
		{
			for (int x=0; x < width; x++)
			{
				// Extract rgb
				int pixel = pixels[y*width + x];
				int r = (pixel >> 16) & 0xff;
				int g = (pixel >> 8) & 0xff;
				int b = pixel & 0xff;

				// Perform image negative.
				r = 255 - r;
				g = 255 - g;
				b = 255 - b;

				// Reform rgb
				pixel = Util.setRGB(r, g, b);
				pixels[width*height] = pixel;
			}
		}

		// Set pixels
		win.getActiveImage().setPixels(pixels, 0, 0,
				width, height);
	}
	
	private int median(int[] array) {
		int max, maxIndex;
		
		for (int i = 0; i < 4; i++) {
			max = 0;
			maxIndex = 0;
			for (int j = 0; j < 9; j++) {
				if (array[j] > max) {
					max = array[j];
					maxIndex = j;
				}
			}
			array[maxIndex] = 0;
		}
		max = 0;
		for (int i = 0; i < 9; i++) {
			if (array[i] > max)
				max = array[i];
		}
		return max;
	}

	private int rgbMedian(int[] r, int[] g, int[] b) {
		int sum, index = 0, min = Integer.MAX_VALUE;
		
		for (int i = 0; i < 9; i++) {
			sum = 0;
			for (int j = 0; j < 9; j++) {
				sum += Math.abs(r[i]-r[j]);
				sum += Math.abs(g[i]-g[j]);
				sum += Math.abs(b[i]-b[j]);
			}
			if (sum < min) {
				min = sum;
				index = i;
			}
		}
		return index;
	}
	
	public void medianFilter(IPWindow win){
		p.print("Applying Median Filter");

		// Get pixels
		int width = win.getActiveImage().getWidth();
		int height = win.getActiveImage().getHeight();
		int [] pixels = new int[width * height];
		win.getActiveImage().getPixels(pixels, 0, 0,
				width, height);

		int index = 0;
		int[] argb = new int[9];
		int[] r = new int[9];
		int[] g = new int[9];
		int[] b = new int[9];

		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int k = 0;
				for (int dy = -1; dy <= 1; dy++) {
					int iy = y+dy;
					if (0 <= iy && iy < height) {
						int ioffset = iy*width;
						for (int dx = -1; dx <= 1; dx++) {
							int ix = x+dx;
							if (0 <= ix && ix < width) {
								int rgb = pixels[ioffset+ix];
								argb[k] = rgb;
								r[k] = (rgb >> 16) & 0xff;
								g[k] = (rgb >> 8) & 0xff;
								b[k] = rgb & 0xff;
								k++;
							}
						}
					}
				}
				while (k < 9) {
					argb[k] = 0xff000000;
					r[k] = g[k] = b[k] = 0;
					k++;
				}
				pixels[index++] = argb[rgbMedian(r, g, b)];
			}
		}
		win.getActiveImage().setPixels(pixels, 0, 0,
				width, height);
	}


	public void contrastStretch(Boolean contrastInc, IPWindow win)
	{
		p.print("Applying contrast stretch");

		// Get pixels
		int width = win.getActiveImage().getWidth();
		int height = win.getActiveImage().getHeight();
		int [] pixels = new int[width * height];
		float scaleFactor = 1.0f;
		win.getActiveImage().getPixels(pixels, 0, 0,
				width, height);
		
        scaleFactor = scaleFactor+0.1f;
        System.out.println(scaleFactor + "=scaleF");
		
		// 2nd pass is to perform scaling.
		for (int y=0; y < height; y++)
		{
			for (int x=0; x < width; x++)
			{
				// Extract rgb
				int pixel = pixels[y*width + x];
				int r = (pixel >> 16) & 0xff;
				int g = (pixel >> 8) & 0xff;
				int b = pixel & 0xff;

				if(contrastInc){
					r = (int)(r * scaleFactor);
					g = (int)(g * scaleFactor);
					b = (int)(b * scaleFactor);
				}
				else{
					r = (int)(r / scaleFactor);
					g = (int)(g / scaleFactor);
					b = (int)(b / scaleFactor);
				}

				// Reform rgb
				pixel = 0xFF000000 | r << 16 | g << 8 | b;
				pixels[y*width + x] = pixel;
			}
		}

		// Set pixels
		win.getActiveImage().setPixels(pixels, 0, 0,
				width, height);
	}


	/**
	 * Called by IPFrame when auto processing is chosen.
	 * @param win IPWindow associated with this event
	 */
	public void autoProcessing(IPWindow win)
	{
		p.print("Auto Processing Stub");
		// TODO: perform auto processing
	}

	/**
	 * Called by IPFrame when manual processing is chosen.
	 * @param win IPWindow associated with this event
	 */
	public void manualProcessing(IPWindow win)
	{
		new ManualPreprocess(win);
	}


	public static void main(String args[])
	{
		try
		{
			new ImageApplication();
		}
		catch (Throwable t)
		{
			System.err.println(t);
			t.printStackTrace();
			//Ensure the application exits with an error condition.
			System.exit(1);
		}
	}
}

